package ucl.cs.stockbroker.server;

/**
 * StockBroker Implementation.
 * @author  Kamil Olesiejuk, Min Park, Saniul Ahmed
 * @version 1.0
 */

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;

import ucl.cs.stockbroker.common.*;

public class StockBrokerImpl implements StockBroker {

	/**
	 * Stock Price updater for the Stock Broker application.
	 * 
	 * @author Min Park, Saniul Ahmed, Kamil Olesiejuk
	 * 
	 */
	private class PriceUpdater extends Thread {

		private URL dataURL;

		String url = "http://quote.yahoo.com/d/quotes.csv?s=VOD.L+MSF.L+BT-A.L+927743.L+ABS.L+BATS.L+RR.L+HMV.L+RBS.L&f=l1sn";

		PriceUpdater() {
			this.initStockData();
		}

		/**
		 * Method initialising the stock database on the Stock Broker Server.
		 */
		public void initStockData() {
			StockBrokerImpl.this.stockData = new ArrayList<StockCompany>();
			this.updateStockData();
			for (final StockCompany company : StockBrokerImpl.this.stockData)
				System.out.println(company.getStockPrice());

		}

		public void run() {
			while (true)
				try {
					sleep(10000);
					this.updateStockData();
				} catch (final InterruptedException e) {
					System.out
							.println("ERROR: Updater Iterrupted. Program exiting.");
					System.exit(1);
				}
		}

		/**
		 * Updates the information of Stock Company objects in regard to the
		 * contemporary state.
		 */
		public void updateStockData() {
			try {
				this.dataURL = new URL(this.url);
			} catch (final MalformedURLException e) {
				System.out
						.println("CRITICAL ERROR: The impossible has happened! The hard-hoded URL was malformed.");
				System.exit(1);
			}
			Scanner scanner;
			try {
				scanner = new Scanner(this.dataURL.openStream());
				while (scanner.hasNextLine())
					try {
						scanner
								.findInLine(Pattern
										.compile("([0-9.]*),\"([0-9a-zA-Z._-]*)\",\"([a-zA-Z._\\s-]*)\""));
						final MatchResult result = scanner.match();
						boolean found = false;
						for (final StockCompany company : StockBrokerImpl.this.stockData)
							if (company.getCompanySymbol().equals(
									result.group(2))) {
								final Double stockPrice = Double
										.parseDouble(result.group(1));
								company.setStockPrice(stockPrice);
								found = true;
							}
						if (!found)
							StockBrokerImpl.this.stockData
									.add(new StockCompany(result.group(3),
											result.group(2), Double
													.parseDouble(result
															.group(1))));

						scanner.nextLine();

					} catch (final NumberFormatException e) {
						System.out
								.println("CRITICAL ERROR: Update data corrupted. [Virtually impossible]");
						System.exit(1);
					}
				scanner.close();
			} catch (final IOException e) {
				System.out
						.println("ERROR: Connection to stock price update feed could not be established.");
			}
		}
	}

	private final Map<Integer, StockBrokerAccount> accounts = new ConcurrentHashMap<Integer, StockBrokerAccount>();
	private ArrayList<StockCompany> stockData;
	private final PriceUpdater priceUpdater;
	static final long serialVersionUID = 1;

	private static int next_account_number = 0;

	/**
	 * StockBrokerImpl constructor. Creates a PriceUpdater object and runs a
	 * thread of PriceUpdater initialising the database, and starting the price
	 * updates at intervals.
	 * 
	 * @throws java.rmi.RemoteException
	 */
	public StockBrokerImpl() throws java.rmi.RemoteException {
		super();
		this.priceUpdater = new PriceUpdater();
		this.priceUpdater.start();
	}

	public synchronized int createAccount(String name, String PIN)
			throws RemoteException {

		StockBrokerImpl.next_account_number++;
		final int account_number = StockBrokerImpl.next_account_number;
		this.threadRest();

		System.out.println("Creating account number " + account_number);
		final StockBrokerAccount account = new StockBrokerAccountImpl(name,
				account_number, PIN, this);

		this.accounts.put(account_number, account);

		return account_number;
	}

	public StockBrokerAccount getAccount(int accountNumber)
			throws RemoteException, NoSuchAccountException {
		if (accounts.get(accountNumber) == null) {
			throw new NoSuchAccountException();
		}
		return this.accounts.get(accountNumber);
	}

	public ArrayList<StockCompanyInfo> getRegisteredStock()
			throws RemoteException {
		final ArrayList<StockCompanyInfo> stockInfo = new ArrayList<StockCompanyInfo>();
		for (final StockCompany company : this.stockData)
			stockInfo.add(new StockCompanyInfo(company.getName(), company
					.getCompanySymbol(), company.getStockPrice()));
		return stockInfo;
	}

	public StockBrokerAccountInfo[] listAccounts() throws RemoteException {
		final StockBrokerAccountInfo[] accountInfo = new StockBrokerAccountInfo[this.accounts
				.size()];
		int i = 0;
		for (final StockBrokerAccount account : this.accounts.values()) {
			accountInfo[i] = new StockBrokerAccountInfo(account.getName(),
					account.getAccountNumber(), account.getBalance(), account
							.calculateAccountValue(), account.getOwnedStock());
			i++;
		}
		return accountInfo;
	}

	public StockBrokerAccountInfo[] listAccountsRanked() throws RemoteException {
		StockBrokerAccountInfo[] accountInfo = new StockBrokerAccountInfo[this.accounts
				.size()];
		ArrayList<StockBrokerAccountImpl> sorted = new ArrayList<StockBrokerAccountImpl>();
		for (StockBrokerAccount account : this.accounts.values()) {
			sorted.add((StockBrokerAccountImpl) account);
		}
		Collections.sort(sorted);
		int i = 0;
		for (StockBrokerAccount account : sorted) {
			accountInfo[i] = new StockBrokerAccountInfo(account.getName(),
					account.getAccountNumber(), account.getBalance(), account
							.calculateAccountValue(), account.getOwnedStock());
			i++;
		}
		return accountInfo;
	}

	public double terminateAccount(int accountNumber, String PIN)
			throws RemoteException, NoSuchAccountException, WrongPINException {
		StockBrokerAccount acct = this.getAccount(accountNumber);
		if (!(acct.isPINValid(PIN)))
			throw new WrongPINException();

		System.out.println("Deleting account number " + accountNumber);
		System.out.println("Selling all stock...");
		try {
			for (final String companySymbol : this.accounts.get(accountNumber)
					.getOwnedStock().keySet())
				this.accounts.get(accountNumber).sellStock(PIN, companySymbol);
		} catch (NoSuchCompanyException e) {
			System.out.println("CRITICAL ERROR: Data corrupted!");
			// e.printStackTrace();
		} catch (NotEnoughStockException e) {
			System.out.println("CRITICAL ERROR: Data corrupted!");
			// e.printStackTrace();
		}

		final double finalBalance = this.accounts.get(accountNumber)
				.getBalance();
		System.out.println("The total balance on termination is: "
				+ finalBalance);
		System.out.println("Account number " + accountNumber + " removed.");
		this.accounts.remove(accountNumber);
		return finalBalance;

	}

	private void threadRest() {
		try {
			Thread.sleep(5000);
		} catch (final InterruptedException except) {
		}
	}
}